﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Threading;
using System.Windows.Threading;

namespace Kokomo.Controls
{
	public class TreemapPanel : Panel
	{
		static TreemapPanel()
		{
			DefaultStyleKeyProperty.OverrideMetadata(typeof(TreemapPanel), new FrameworkPropertyMetadata(typeof(TreemapPanel)));
			SnapsToDevicePixelsProperty.OverrideMetadata(typeof(TreemapPanel), new FrameworkPropertyMetadata(true));
		}

		public TreemapPanel()
		{
		}

		public float BranchSize { get; private set; }

		private Rect CalculateItemRect(
			float percentage,
			bool isHorizontal,
			ref Rect layoutRect,
			out float aspectRatio)
		{
			percentage = Math.Max(0, Math.Min(1, percentage));
			//if (percentage <= 0 || percentage > 1) throw new ArgumentOutOfRangeException("percentage", percentage, "0.0 < percentage <= 1.0");

			Rect itemRect;
			if (isHorizontal)
			{
				float width = (float)Math.Round(layoutRect.Width * percentage);

				itemRect = new Rect(
					layoutRect.Left,
					layoutRect.Top,
					width,
					layoutRect.Height
					);
				layoutRect = new Rect(
					itemRect.Right,
					layoutRect.Top,
					layoutRect.Width - width,
					layoutRect.Height
					);
				aspectRatio = (float)(itemRect.Height / itemRect.Width);
			}
			else
			{
				double height = Math.Round(layoutRect.Height * percentage);
				itemRect = new Rect(
					layoutRect.Left,
					layoutRect.Top,
					layoutRect.Width,
					height
					);
				layoutRect = new Rect(
					layoutRect.Left,
					itemRect.Bottom,
					layoutRect.Width,
					layoutRect.Height - height
					);
				aspectRatio = (float)(itemRect.Width / itemRect.Height);
			}

			return itemRect;
		}

		//private void SquareArrange(IEnumerable<TreemapItem> items, Rect layoutRect, double remainingSize)
		//{
		//    if (remainingSize == 0) return;// throw new ArgumentNullException("remainingSize");

		//    items = items.OrderByDescending(item => item.BranchSize);

		//    double squareCapacity = remainingSize * (
		//        (layoutRect.Width > layoutRect.Height)
		//        ? (layoutRect.Height / layoutRect.Width)
		//        : (layoutRect.Width / layoutRect.Height)
		//        );

		//    List<TreemapItem> squareItems = new List<TreemapItem>();
		//    List<TreemapItem> otherItems = new List<TreemapItem>();
		//    double squareSize = 0;
		//    foreach (var item in items)
		//    {
		//        double itemSize = item.BranchSize;
		//        if ((squareSize + itemSize) > squareCapacity)
		//        {
		//            otherItems.Add(item);
		//        }
		//        else
		//        {
		//            squareItems.Add(item);
		//            squareSize += itemSize;
		//        }
		//    }

		//    if (squareSize > 0)
		//    {
		//        TreemapItem firstItem = squareItems[0];
		//        squareItems.Remove(firstItem);
		//        Rect squareRect = CalculateItemRect(ref layoutRect, squareSize / remainingSize);
		//        Rect firstRect = CalculateItemRect(ref squareRect, firstItem.BranchSize / squareSize);

		//        firstItem.Arrange(firstRect);

		//        if (squareItems.Count > 0)
		//        {
		//            SquareArrange(squareItems, squareRect, squareSize - firstItem.BranchSize);
		//        }
		//        if (otherItems.Count > 0)
		//        {
		//            SquareArrange(otherItems, layoutRect, remainingSize - squareSize);
		//        }
		//    }
		//    else
		//    {
		//        if (otherItems.Count > 0)
		//        {
		//            ShellArrange(otherItems, layoutRect, remainingSize);
		//        }
		//    }
		//}

		private void SquareArrange(
			IEnumerable<TreemapItem> items,
			Rect layoutRect,
			float remainingSize
			)
		{
			int mapVersion = this._mapVersion;
			bool isLayoutHorizontal = layoutRect.Width > layoutRect.Height;

			List<TreemapItem> committedItems = new List<TreemapItem>();
			float committedSize = 0;
			float committedAspectRation = float.PositiveInfinity;
			Rect committedItemRect = new Rect();
			Rect committedLayoutRect = new Rect();
			foreach (var item in items)
			{
				while (true)
				{
					if (this._mapVersion != mapVersion) return;

					float trialSize = committedSize + item._branchSize;
					float trialAspectRatio;
					Rect trialLayoutRect = layoutRect;
					Rect trialItemRect = this.CalculateItemRect(
						trialSize / remainingSize,
						isLayoutHorizontal,
						ref trialLayoutRect,
						out trialAspectRatio
						);
					trialAspectRatio /= committedItems.Count + 1;
					if (trialAspectRatio < 1) trialAspectRatio = 1 / trialAspectRatio;

					if (trialAspectRatio > committedAspectRation)
					{
						this.Dispatcher.Invoke(
							(Action<IEnumerable<TreemapItem>, Rect, float>)this.LinearArrange,
							DispatcherPriority.Input - 1,
							committedItems,
							committedItemRect,
							committedSize
							);
						//this.LinearArrange(committedItems, committedItemRect, committedSize);
						committedItems.Clear();
						//committedSize = 0;
						committedAspectRation = float.PositiveInfinity;
						layoutRect = committedLayoutRect;

						remainingSize -= committedSize;
						isLayoutHorizontal = layoutRect.Width > layoutRect.Height;

						committedSize = 0;
					}
					else
					{
						committedItems.Add(item);
						committedSize = trialSize;
						committedAspectRation = trialAspectRatio;
						committedItemRect = trialItemRect;
						committedLayoutRect = trialLayoutRect;

						break;
					}
				}
			}

			if (committedItems.Count > 0)
				//this.LinearArrange(committedItems, layoutRect, committedSize);
				this.Dispatcher.Invoke(
					(Action<IEnumerable<TreemapItem>, Rect, float>)this.LinearArrange,
					DispatcherPriority.Input - 1,
					committedItems,
					layoutRect,
					committedSize
					);
		}

		private void LinearArrange(
			IEnumerable<TreemapItem> items,
			Rect layoutRect,
			float remainingSize
			)
		{
			bool isLayoutHorizontal = layoutRect.Width > layoutRect.Height;

			foreach (var item in items)
			{
				float itemBranchSize = item._branchSize;
				if (itemBranchSize > 0)
				{
					float aspectRatio;
					Rect itemRect;
					if (itemBranchSize == remainingSize)
					{
						itemRect = layoutRect;
					}
					else
					{
						itemRect = this.CalculateItemRect(
							(float)itemBranchSize / remainingSize,
							isLayoutHorizontal,
							ref layoutRect,
							out aspectRatio
							);
					}


					//this.Dispatcher.BeginInvoke((Action<FrameworkElement, Rect>)MeasureArrange, System.Windows.Threading.DispatcherPriority.Render, item, itemRect);
					item.Measure(itemRect.Size);
					item.Arrange(itemRect);

					remainingSize -= itemBranchSize;
				}
			}
		}

		private static void MeasureArrange(FrameworkElement element, Rect rect)
		{
			element.Measure(rect.Size);
			element.Arrange(rect);
		}

		//private void ShellArrange(IEnumerable<TreemapItem> items, Rect layoutRect, double remainingSize)
		//{
		//    //if (remainingSize == 0) throw new ArgumentNullException("remainingSize");

		//    items = items.OrderByDescending(item => item.BranchSize);
		//    TreemapItem lastItem = items.LastOrDefault();

		//    foreach (var item in items)
		//    {
		//        if (item == lastItem) break;
		//        if (item.BranchSize == 0) continue;

		//        double percentage = item.BranchSize / remainingSize;

		//        double aspectRatio;
		//        Rect itemRect = CalculateItemRect(percentage, ref layoutRect, out aspectRatio);

		//        double itemBranchSizeBeforeArrange = item.BranchSize;
		//        item.Arrange(itemRect);
		//        if (item.BranchSize != itemBranchSizeBeforeArrange) { }
		//        remainingSize -= item.BranchSize;
		//    }
		//    if (lastItem != null)
		//    {
		//        lastItem.Arrange(layoutRect);
		//    }
		//}

		private TreemapItem[] _orderedItems;

		protected override Size MeasureOverride(Size availableSize)
		{
			this._orderedItems = this.Children.OfType<TreemapItem>().OrderByDescending(item => item._branchSize).ToArray();
			Size returnValue = base.MeasureOverride(availableSize);

			//foreach (var item in this.Children)
			//{
			//    UIElement element = item as UIElement;
			//    if (element != null) element.Measure(availableSize);
			//}

			return returnValue;
		}

		public event EventHandler Remap;
		protected virtual void OnRemap()
		{
			this._mapVersion++;
			new Thread(this.Map) { Name = "TreemapPanel.Map" }.Start();
			if (this.Remap != null) this.Remap(this, EventArgs.Empty);
		}

		private int _mapVersion;

		private void Map()
		{
			Size finalSize = this.RenderSize;

			//var items = this.Children.OfType<TreemapItem>().ToArray();
			var items = this._orderedItems;

			float totalArea = (float)(finalSize.Width * finalSize.Height);

			//foreach (var item in items)
			//{
			//    item.Measure(finalSize);
			//}

			float minArea = 100;

			Rect layoutRect = new Rect(new Point(0, 0), finalSize);
			List<TreemapItem> significantItems = new List<TreemapItem>(items.Length);
			float totalBranchSize = 0;
			foreach (var item in items)
			{
				float itemBranchSize = item._branchSize;
				if (itemBranchSize == 0) continue;

				if (totalBranchSize > 0)
				{
					float itemArea = totalArea * (itemBranchSize / totalBranchSize);
					if (itemArea < minArea) continue;
				}

				significantItems.Add(item);
				totalBranchSize += itemBranchSize;
			}

			//double totalBranchSize = items.Sum(item => item.BranchSize);
			if (
				(significantItems.Count > 0)
				&& (totalBranchSize > 0)
				)
			{
				//this.ShellArrange(items, layoutRect, totalBranchSize);
				this.SquareArrange(significantItems, layoutRect, totalBranchSize);
			}


			//    this.SquareArrange(items, layoutRect, totalBranchSize);
		}

		protected override System.Windows.Size ArrangeOverride(System.Windows.Size finalSize)
		{
			this.OnRemap();
			return base.ArrangeOverride(finalSize);
		}
	}
}
